#define _POSIX_C_SOURCE 200809L
#include "server.h"
#include "wlroots-full.hpp"
#include "util.h"
#include "output.h"
#include "output_wrapper.h"
#include "toplevel_wrapper.h"
#include "cursor_wrapper.h"
#include "keyboard_wrapper.h"
#include "seat_wrapper.h"
#include "seat.h"
#include <glib.h>
#include <gio/gio.h>
#include <glib-unix.h>

#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <libinput.h>

#define BWL_WLR_COMPOSITOR_VERSION 5
#define BWL_WLR_FRACTIONAL_SCALE_V1_VERSION 1
#define BWL_XDG_SHELL_VERSION (3)
#define CONFIGURE_TIMEOUT_MS 100

#if 0
void server_quit(Server *server)
{
    wl_display_terminate(m_display);
}
#endif

Server::Server()
{
    m_display = wl_display_create();
#if 0
    m_glib_loop = g_main_loop_new(NULL, FALSE);
    GSource* source = wl_glib_source_new(m_display);
    g_source_attach(source, NULL);
    g_source_unref(source);

    g_unix_signal_add(SIGINT, handle_sigterm, this);
    g_unix_signal_add(SIGTERM, handle_sigterm, this);
#endif
    if (!(m_backend = wlr_backend_autocreate(m_display, &m_session))) {
        wlr_log(WLR_ERROR, "couldn't create backend");
        exit(EXIT_FAILURE);
    }

    m_renderer = wlr_renderer_autocreate(m_backend);
    if (!m_renderer) {
        wlr_log(WLR_ERROR, "unable to create renderer");
        exit(EXIT_FAILURE);
    }
    wlr_renderer_init_wl_display(m_renderer, m_display);

    m_alloc = wlr_allocator_autocreate(
        m_backend, m_renderer);
    if (!m_alloc) {
        wlr_log(WLR_ERROR, "unable to create allocator");
        exit(EXIT_FAILURE);
    }

    m_compositor = wlr_compositor_create(m_display, BWL_WLR_COMPOSITOR_VERSION, m_renderer);
    if (!m_compositor) {
        wlr_log(WLR_ERROR, "unable to create the wlroots compositor");
        exit(EXIT_FAILURE);
    }
    wlr_subcompositor_create(m_display);

    struct wlr_data_device_manager *device_manager = wlr_data_device_manager_create(m_display);
    if (!device_manager) {
        wlr_log(WLR_ERROR, "unable to create data device manager");
        exit(EXIT_FAILURE);
    }

    wlr_primary_selection_v1_device_manager_create(m_display);

    // wl_list_init(&server->views);
    // wl_list_init(&server->unmanaged_surfaces);
    output_init();
    xdg_shell_init();
    input_init();
}

Server::~Server()
{
    wlr_backend_destroy(m_backend);
    wl_display_destroy(m_display);
}

void Server::run(const char *startup_cmd)
{
    pid_t startup_pid = -1;

    /* Add a Unix socket to the Wayland display. */
    const char *socket = wl_display_add_socket_auto(m_display);
    if (!socket)
        die("startup: display_add_socket_auto");
    setenv("WAYLAND_DISPLAY", socket, 1);

    /* Now that the socket exists, run the startup command */
    if (startup_cmd) {
        int piperw[2];
        if (pipe(piperw) < 0)
            die("startup: pipe:");
        if ((startup_pid = fork()) < 0)
            die("startup: fork:");
        if (startup_pid == 0) {
            dup2(piperw[0], STDIN_FILENO);
            close(piperw[0]);
            close(piperw[1]);
            execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL);
            die("startup: execl:");
        }
        dup2(piperw[1], STDOUT_FILENO);
        close(piperw[1]);
        close(piperw[0]);
    }
    /* If nobody is reading the status output, don't terminate */
    signal(SIGPIPE, SIG_IGN);

    /* Start the backend. This will enumerate outputs and inputs, become the DRM
     * master, etc */
    if (!wlr_backend_start(m_backend))
        die("startup: backend_start");

#if 0
    g_main_loop_run(m_glib_loop);
#else
    wl_display_run(m_display);
#endif

    if (startup_cmd) {
        kill(startup_pid, SIGTERM);
        waitpid(startup_pid, NULL, 0);
    }
}

gboolean Server::handle_sigterm(gpointer user_data)
{
    Server* server = (Server*)user_data;
    g_main_loop_quit(server->m_glib_loop);
    return G_SOURCE_REMOVE;
}

void Server::new_output(struct wlr_output *wlr_output)
{
#if 0
    /*
     * This event is rasied by the backend when a new output (aka display
     * or monitor) becomes available.
     */
    struct server *server = wl_container_of(listener, server, new_output);
    struct wlr_output *wlr_output = data;

    /* Name virtual output */
    if (wlr_output_is_headless(wlr_output) && server->headless.pending_output_name[0] != '\0') {
        wlr_output_set_name(wlr_output, server->headless.pending_output_name);
        server->headless.pending_output_name[0] = '\0';
    }

    /*
     * We offer any display as available for lease, some apps like
     * gamescope, want to take ownership of a display when they can
     * to use planes and present directly.
     * This is also useful for debugging the DRM parts of
     * another compositor.
     */
    if (server->drm_lease_manager && wlr_output_is_drm(wlr_output)) {
        wlr_drm_lease_v1_manager_offer_output(
            server->drm_lease_manager, wlr_output);
    }

    /*
     * Don't configure any non-desktop displays, such as VR headsets;
     */
    if (wlr_output->non_desktop) {
        wlr_log(WLR_DEBUG, "Not configuring non-desktop output");
        return;
    }
#endif
    /*
     * Configures the output created by the backend to use our allocator
     * and our renderer. Must be done once, before committing the output
     */
    if (!wlr_output_init_render(wlr_output, m_alloc, m_renderer)) {
        wlr_log(WLR_ERROR, "unable to init output renderer");
        return;
    }

    // wlr_log(WLR_DEBUG, "enable output");
    // wlr_output_enable(wlr_output, true);

    /*
     * Try to re-use the existing mode if configured to do so.
     * Failing that, try to set the preferred mode.
     */
#if 0
    struct wlr_output_mode *preferred_mode = NULL;
    if (!rc.reuse_output_mode || !can_reuse_mode(wlr_output)) {
        wlr_log(WLR_DEBUG, "set preferred mode");
        /* The mode is a tuple of (width, height, refresh rate). */
        preferred_mode = wlr_output_preferred_mode(wlr_output);
        wlr_output_set_mode(wlr_output, preferred_mode);
    }

    /*
     * Sometimes the preferred mode is not available due to hardware
     * constraints (e.g. GPU or cable bandwidth limitations). In these
     * cases it's better to fallback to lower modes than to end up with
     * a black screen. See sway@4cdc4ac6
     */
    if (!wlr_output_test(wlr_output)) {
        wlr_log(WLR_DEBUG,
                "preferred mode rejected, falling back to another mode");
        struct wlr_output_mode *mode;
        wl_list_for_each(mode, &wlr_output->modes, link) {
            if (mode == preferred_mode) {
                continue;
            }
            wlr_output_set_mode(wlr_output, mode);
            if (wlr_output_test(wlr_output)) {
                break;
            }
        }
    }

    if (rc.adaptive_sync == LAB_ADAPTIVE_SYNC_ENABLED) {
        output_enable_adaptive_sync(wlr_output, true);
    }
#endif

    // wlr_output_commit(wlr_output);

    /* The output may be disabled, switch it on. */
    struct wlr_output_state state;
    wlr_output_state_init(&state);
    wlr_output_state_set_enabled(&state, true);

    /* Some backends don't have modes. DRM+KMS does, and we need to set a mode
     * before we can use the output. The mode is a tuple of (width, height,
     * refresh rate), and each monitor supports only a specific set of modes. We
     * just pick the monitor's preferred mode, a more sophisticated compositor
     * would let the user configure it. */
    struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output);
    if (mode != NULL) {
        wlr_output_state_set_mode(&state, mode);
    }

    /* Atomically applies the new output state. */
    wlr_output_commit_state(wlr_output, &state);
    wlr_output_state_finish(&state);


    OutputWrapper* output = new OutputWrapper(wlr_output, this);
    output->sig_destroy.connect([&]() {
        m_outputs.remove(output);
    });
    m_outputs.push_back(output);
#if 0
    /*
     * Wait until wlr_output_layout_add_auto() returns before
     * calling do_output_layout_change(); this ensures that the
     * wlr_output_cursor is created for the new output.
     */
    m_pending_output_layout_change++;

    add_output_to_layout(output);

    /* Create regions from config */
    regions_reconfigure_output(output);

    if (server->session_lock) {
        session_lock_output_create(server->session_lock, output);
    }

    m_pending_output_layout_change--;
    do_output_layout_change(server);
    seat_output_layout_changed(&output->server->seat);
#endif
}

void Server::output_layout_change()
{
#if 0
    if (!m_pending_output_layout_change) {
        struct wlr_output_configuration_v1 *config =
            create_output_config(server);
        if (config) {
            wlr_output_manager_v1_set_configuration(
                server->output_manager, config);
        } else {
            wlr_log(WLR_ERROR,
                    "wlr_output_manager_v1_set_configuration()");
        }
        output_update_for_layout_change(server);
    }
#endif
}

void Server::output_init()
{
    // m_gamma_control_manager_v1 = wlr_gamma_control_manager_v1_create(m_display);

    m_output_layout = wlr_output_layout_create();
    if (!m_output_layout) {
        wlr_log(WLR_ERROR, "unable to create output layout");
        exit(EXIT_FAILURE);
    }

    m_new_output.connect(&m_backend->events.new_output, [=](void* data) {
        struct wlr_output *wlr_output = (struct wlr_output*)data;
        new_output(wlr_output);
    });

    m_scene = wlr_scene_create();
    if (!m_scene) {
        wlr_log(WLR_ERROR, "unable to create scene");
        exit(EXIT_FAILURE);
    }

    m_scene_layout = wlr_scene_attach_output_layout(m_scene, m_output_layout);
    if (!m_scene_layout) {
        wlr_log(WLR_ERROR, "unable to create scene layout");
        exit(EXIT_FAILURE);
    }

    /* Enable screen recording with wf-recorder */
    wlr_xdg_output_manager_v1_create(m_display, m_output_layout);

    // output_manager_init(server);
#if 0
    m_output_manager = wlr_output_manager_v1_create(m_display);
    m_layout_change.connect(&m_output_layout->events.change, [=](void* data) {
        output_layout_change();
    });

    server->output_manager_apply.notify = handle_output_manager_apply;
    wl_signal_add(&server->output_manager->events.apply,
                  &server->output_manager_apply);

    server->output_manager_test.notify = handle_output_manager_test;
    wl_signal_add(&server->output_manager->events.test,
                  &server->output_manager_test);

    server->gamma_control_set_gamma.notify = handle_gamma_control_set_gamma;
    wl_signal_add(&server->gamma_control_manager_v1->events.set_gamma,
                  &server->gamma_control_set_gamma);
#endif
}

void Server::xdg_shell_init()
{
    m_xdg_shell = wlr_xdg_shell_create(m_display,
                                             BWL_XDG_SHELL_VERSION);
    if (!m_xdg_shell) {
        wlr_log(WLR_ERROR, "unable to create the XDG shell interface");
        exit(EXIT_FAILURE);
    }

    m_new_xdg_surface.connect(&m_xdg_shell->events.new_surface, [=](void* data) {
        struct wlr_xdg_surface *xdg_surface = (struct wlr_xdg_surface*)data;
        new_xdg_surface(xdg_surface);
    });

    m_xdg_activation = wlr_xdg_activation_v1_create(m_display);
    if (!m_xdg_activation) {
        wlr_log(WLR_ERROR, "unable to create xdg_activation interface");
        exit(EXIT_FAILURE);
    }
#if 0
    server->xdg_activation_request.notify = xdg_activation_handle_request;
    wl_signal_add(&server->xdg_activation->events.request_activate,
                  &server->xdg_activation_request);
#endif
}

void Server::new_xdg_surface(struct wlr_xdg_surface *xdg_surface)
{
#if 0
    /* We must add xdg popups to the scene graph so they get rendered. The
     * wlroots scene graph provides a helper for this, but to use it we must
     * provide the proper parent scene node of the xdg popup. To enable this,
     * we always set the user data field of xdg_surfaces to the corresponding
     * scene node. */
    if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) {
        struct wlr_xdg_surface *parent =
            wlr_xdg_surface_try_from_wlr_surface(xdg_surface->popup->parent);
        assert(parent != NULL);
        struct wlr_scene_tree *parent_tree = parent->data;
        xdg_surface->data = wlr_scene_xdg_surface_create(
            parent_tree, xdg_surface);
        return;
    }
    assert(xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL);
#endif
    ToplevelWrapper* t = new ToplevelWrapper(xdg_surface, this);
    t->sig_toplevel_map.connect(sigc::mem_fun(*this, &Server::focus_toplevel));
    t->sig_toplevel_unmap.connect(sigc::mem_fun(*this, &Server::toplevel_unmap));
    //destroy
    t->sig_toplevel_request_move.connect(sigc::mem_fun(*this, &Server::toplevel_request_move));
    t->sig_toplevel_request_resize.connect(sigc::mem_fun(*this, &Server::toplevel_request_resize));
    t->sig_toplevel_request_maximize.connect(sigc::mem_fun(*this, &Server::toplevel_request_maximize));
    t->sig_toplevel_request_fullscreen.connect(sigc::mem_fun(*this, &Server::toplevel_request_fullscreen));
}

void Server::new_input(wlr_input_device *device)
{
    switch (device->type) {
    case WLR_INPUT_DEVICE_KEYBOARD:
        new_keyboard(device);
        break;
    case WLR_INPUT_DEVICE_POINTER:
        new_pointer(device);
        break;
    default:
        break;
    }
    /* We need to let the wlr_seat know what our capabilities are, which is
     * communiciated to the client. In TinyWL we always have a cursor, even if
     * there are no pointer devices, so we always include that capability. */
#if 1
    uint32_t caps = WL_SEAT_CAPABILITY_POINTER;
    if (!m_keyboards.empty()) {
        caps |= WL_SEAT_CAPABILITY_KEYBOARD;
    }
    wlr_seat_set_capabilities(m_seat->seat(), caps);
#endif
}

void Server::new_pointer(wlr_input_device *device)
{
    /* We don't do anything special with pointers. All of our pointer handling
     * is proxied through wlr_cursor. On another compositor, you might take this
     * opportunity to do libinput configuration on the device to set
     * acceleration, etc. */
    wlr_cursor_attach_input_device(m_cursor->cursor(), device);
}

void Server::new_keyboard(wlr_input_device *device)
{
    KeyboardWrapper *keyboard = new KeyboardWrapper(device, this);
    keyboard->sig_key.connect(sigc::mem_fun(*this, &Server::on_key));
    keyboard->sig_modifiers.connect(sigc::mem_fun(*this, &Server::on_modifiers));
    keyboard->sig_destroy.connect([&]() {
        m_keyboards.remove(keyboard);
    });
    wlr_seat_set_keyboard(m_seat->seat(), keyboard->keyboard());
    m_keyboards.push_back(keyboard);
}

void Server::input_init()
{
    m_new_input.connect(&m_backend->events.new_input, [=](void* data) {
        struct wlr_input_device* device = (struct wlr_input_device*)data;
        new_input(device);
    });

    m_cursor = new CursorWrapper(this);
    m_cursor->sig_cursor_motion.connect(sigc::mem_fun(*this, &Server::on_cursor_motion));
    m_cursor->sig_cursor_button.connect(sigc::mem_fun(*this, &Server::on_cursor_button));
    m_cursor->sig_cursor_axis.connect(sigc::mem_fun(*this, &Server::on_cursor_axis));
    m_cursor->sig_cursor_frame.connect(sigc::mem_fun(*this, &Server::on_cursor_frame));
    m_cursor->sig_cursor_move.connect(sigc::mem_fun(*this, &Server::on_cursor_move));
    m_cursor->sig_cursor_resize.connect(sigc::mem_fun(*this, &Server::on_cursor_resize));

    m_seat = new SeatWrapper(this);
    m_seat->sig_request_cursor.connect(sigc::mem_fun(*this, &Server::on_request_cursor));
}

ToplevelWrapper *Server::desktop_toplevel_at(double lx, double ly, wlr_surface **surface, double *sx, double *sy)
{
    /* This returns the topmost node in the scene at the given layout coords.
     * We only care about surface nodes as we are specifically looking for a
     * surface in the surface tree of a tinywl_toplevel. */
    struct wlr_scene_node *node = wlr_scene_node_at(
        &m_scene->tree.node, lx, ly, sx, sy);
    if (node == NULL || node->type != WLR_SCENE_NODE_BUFFER) {
        return NULL;
    }
    struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
    struct wlr_scene_surface *scene_surface =
        wlr_scene_surface_try_from_buffer(scene_buffer);
    if (!scene_surface) {
        return NULL;
    }

    *surface = scene_surface->surface;
    /* Find the node corresponding to the tinywl_toplevel at the root of this
     * surface tree, it is the only one for which we set the data field. */
    struct wlr_scene_tree *tree = node->parent;
    while (tree != NULL && tree->node.data == NULL) {
        tree = tree->node.parent;
    }
    return (ToplevelWrapper*)tree->node.data;
}

void Server::on_cursor_motion(double x, double y, uint32_t time)
{
    /* Otherwise, find the toplevel under the pointer and send the event along. */
    double sx, sy;
    struct wlr_surface *surface = NULL;
    ToplevelWrapper *toplevel = desktop_toplevel_at(x, y, &surface, &sx, &sy);
    if (!toplevel) {
        /* If there's no toplevel under the cursor, set the cursor image to a
         * default. This is what makes the cursor image appear when you move it
         * around the screen, not over any toplevels. */
        m_cursor->set_cursor_default();
    }
    if (surface) {
        /*
         * Send pointer enter and motion events.
         *
         * The enter event gives the surface "pointer focus", which is distinct
         * from keyboard focus. You get pointer focus by moving the pointer over
         * a window.
         *
         * Note that wlroots will avoid sending duplicate enter/motion events if
         * the surface has already has pointer focus or if the client is already
         * aware of the coordinates passed.
         */
        wlr_seat_pointer_notify_enter(m_seat->seat(), surface, sx, sy);
        wlr_seat_pointer_notify_motion(m_seat->seat(), time, sx, sy);
    } else {
        /* Clear pointer focus so future button events and such are not sent to
         * the last client to have the cursor over it. */
        wlr_seat_pointer_clear_focus(m_seat->seat());
    }
}

void Server::on_cursor_button(double x, double y, struct wlr_pointer_button_event *event)
{
    /* Notify the client with pointer focus that a button press has occurred */
    wlr_seat_pointer_notify_button(m_seat->seat(),
                                   event->time_msec, event->button, event->state);
    double sx, sy;
    struct wlr_surface *surface = NULL;
    ToplevelWrapper *toplevel = desktop_toplevel_at(x, y, &surface, &sx, &sy);
    if (event->state == WLR_BUTTON_RELEASED) {
        /* If you released any buttons, we exit interactive move/resize mode. */
        reset_cursor_mode();
    } else {
        /* Focus that client if the button was _pressed_ */
        focus_toplevel(toplevel, surface);
    }
}

void Server::on_cursor_axis(wlr_pointer_axis_event *event)
{
    wlr_seat_pointer_notify_axis(m_seat->seat(),
                                 event->time_msec, event->orientation, event->delta,
                                 event->delta_discrete, event->source);
}

void Server::reset_cursor_mode()
{
    /* Reset the cursor mode to passthrough. */
    m_cursor->set_cursor_mode(BWL_CURSOR_PASSTHROUGH);
    m_grabbed_toplevel = nullptr;
}

void Server::on_cursor_frame()
{
    wlr_seat_pointer_notify_frame(m_seat->seat());
}

void Server::on_cursor_move(int x, int y)
{
    /* Move the grabbed toplevel to the new position. */
    m_grabbed_toplevel->set_position(x, y);
}

void Server::on_cursor_resize(int left, int right, int top, int bottom, uint32_t time)
{
    struct wlr_box geo_box = m_grabbed_toplevel->get_base_geometry();
    m_grabbed_toplevel->set_position(left - geo_box.x, top - geo_box.y);
    m_grabbed_toplevel->set_size(right - left, bottom - top);
}

void Server::on_request_cursor(wlr_surface *surface, int hotspot_x, int hotspot_y)
{
    wlr_cursor_set_surface(m_cursor->cursor(), surface, hotspot_x, hotspot_y);
}

void Server::on_modifiers(wlr_keyboard *keyboard)
{
    wlr_seat_set_keyboard(m_seat->seat(), keyboard);
    /* Send modifiers to the client. */
    wlr_seat_keyboard_notify_modifiers(m_seat->seat(),
                                       &keyboard->modifiers);
}

void Server::on_key(wlr_keyboard *keyboard, wlr_keyboard_key_event *event)
{
#ifdef ALT_ESC_QUIT
    /* Translate libinput keycode -> xkbcommon */
    uint32_t keycode = event->keycode + 8;
    /* Get a list of keysyms based on the keymap for this keyboard */
    const xkb_keysym_t *syms;
    int nsyms = xkb_state_key_get_syms(keyboard->xkb_state, keycode, &syms);

    bool handled = false;
    uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard);
    if ((modifiers & WLR_MODIFIER_ALT) &&
        event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
        /* If alt is held down and this button was _pressed_, we attempt to
         * process it as a compositor keybinding. */
        for (int i = 0; i < nsyms; i++) {
            handled = handle_keybinding(syms[i]);
        }
    }

    if (!handled) {
#endif
        /* Otherwise, we pass it along to the client. */
    wlr_seat_set_keyboard(m_seat->seat(), keyboard);
    wlr_seat_keyboard_notify_key(m_seat->seat(), event->time_msec, event->keycode, event->state);
#ifdef ALT_ESC_QUIT
    }
#endif
}

#ifdef ALT_ESC_QUIT
bool Server::handle_keybinding(xkb_keysym_t sym)
{
    switch (sym) {
    case XKB_KEY_Escape:
        wl_display_terminate(m_display);
        break;
#if 0
    case XKB_KEY_F1:
        /* Cycle to the next toplevel */
        if (wl_list_length(&server->toplevels) < 2) {
            break;
        }
        struct tinywl_toplevel *next_toplevel =
            wl_container_of(server->toplevels.prev, next_toplevel, link);
        focus_toplevel(next_toplevel, next_toplevel->xdg_toplevel->base->surface);
        break;
#endif
    default:
        return false;
    }
    return true;
}
#endif

void Server::focus_toplevel(ToplevelWrapper *toplevel, wlr_surface *surface)
{
    /* Note: this function only deals with keyboard focus. */
    if (toplevel == NULL) {
        return;
    }
    struct wlr_surface *prev_surface = m_seat->seat()->keyboard_state.focused_surface;
    if (prev_surface == surface) {
        /* Don't re-focus an already focused surface. */
        return;
    }
    if (prev_surface) {
        /*
         * Deactivate the previously focused surface. This lets the client know
         * it no longer has focus and the client will repaint accordingly, e.g.
         * stop displaying a caret.
         */
        struct wlr_xdg_toplevel *prev_toplevel =
            wlr_xdg_toplevel_try_from_wlr_surface(prev_surface);
        if (prev_toplevel != NULL) {
            wlr_xdg_toplevel_set_activated(prev_toplevel, false);
        }
    }

    toplevel->move_toplevel_to_front();
    toplevel->activate();
    /*
     * Tell the seat to have the keyboard enter this surface. wlroots will keep
     * track of this and automatically send key events to the appropriate
     * clients without additional work on your part.
     */
    struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(m_seat->seat());
    if (keyboard != NULL) {
        wlr_seat_keyboard_notify_enter(m_seat->seat(), toplevel->base_surface(),
                                       keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers);
    }
}

void Server::toplevel_unmap(ToplevelWrapper *toplevel)
{
    /* Reset the cursor mode if the grabbed toplevel was unmapped. */
    if (toplevel == m_grabbed_toplevel) {
        reset_cursor_mode();
    }
}

void Server::toplevel_request_move(ToplevelWrapper *toplevel)
{
    begin_interactive(toplevel, BWL_CURSOR_MOVE, 0);
}

void Server::toplevel_request_resize(ToplevelWrapper *toplevel, uint32_t edges)
{
    begin_interactive(toplevel, BWL_CURSOR_RESIZE, edges);
}

void Server::toplevel_request_maximize(ToplevelWrapper *toplevel)
{
    //TODO
}

void Server::toplevel_request_fullscreen(ToplevelWrapper *toplevel)
{
    //TODO
}

void Server::begin_interactive(ToplevelWrapper *toplevel, enum cursor_mode mode, uint32_t edges)
{
    struct wlr_surface *focused_surface = m_seat->seat()->pointer_state.focused_surface;
    if (toplevel->base_surface() != wlr_surface_get_root_surface(focused_surface)) {
        /* Deny move/resize requests from unfocused clients. */
        return;
    }

    m_grabbed_toplevel = toplevel;
    m_cursor->set_cursor_mode(mode);

    if (mode == BWL_CURSOR_MOVE) {
        m_cursor->set_grab_x(m_cursor->x() - toplevel->scene_tree_node_x());
        m_cursor->set_grab_y(m_cursor->y() - toplevel->scene_tree_node_y());
    } else {
        struct wlr_box geo_box = toplevel->get_base_geometry();
        double border_x = (toplevel->scene_tree_node_x() + geo_box.x) +
                          ((edges & WLR_EDGE_RIGHT) ? geo_box.width : 0);
        double border_y = (toplevel->scene_tree_node_y() + geo_box.y) +
                          ((edges & WLR_EDGE_BOTTOM) ? geo_box.height : 0);
        m_cursor->set_grab_x(m_cursor->x() - border_x);
        m_cursor->set_grab_y(m_cursor->y() - border_y);

        m_cursor->set_grab_geobox(geo_box);
        m_cursor->inc_grab_geobox_x(toplevel->scene_tree_node_x());
        m_cursor->inc_grab_geobox_y(toplevel->scene_tree_node_y());
        m_cursor->set_resize_edges(edges);
    }
}
